#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sobjc-commons/defines.h>
#include <sobjc-commons/utils.h>
#include "scope.h"
#include "command.h"
#include "name.h"
#include "type.h"

static int next_method_def = 0;
static objc_scope_entry_t self_entry = NULL;
const char *gcc_builtin_types[] = {
  "__builtin_va_list",
  NULL
};

objc_scope_t
objc_make_root_scope()
{
  const char **names;
  objc_scope_t scope;
  objc_scope_entry_t entry;

  scope = objc_make_scope(NULL);
  if (objc_main_opts.cc == OBJC_CC_GCC) {
    names = gcc_builtin_types;
    while (*names) {
      entry = ALLOC(struct objc_scope_entry);
      entry->type = OBJC_COMPILER_BUILTIN;
      entry->ctype = NULL;
      st_insert(scope->type_table,
                (st_data_t)objc_strdup(*names),
                (st_data_t)entry);
      names++;
    }
  }

  return scope;
}

objc_scope_t
objc_make_scope(objc_scope_t parent)
{
  objc_scope_t scope;

  scope = ALLOC(struct objc_scope);
  scope->parent = parent;
  scope->type_table = st_init_strtable();
  scope->struct_table = st_init_strtable();
  scope->cls = NULL;

  return scope;
}

objc_scope_t
objc_make_meth_def_scope(objc_scope_t parent)
{
  objc_scope_t sc;

  sc = objc_make_scope(parent);
  if (self_entry == NULL) {
    self_entry = ALLOC(struct objc_scope_entry);
    self_entry->type = OBJC_SELF;
    self_entry->name = objc_strdup("self");
    self_entry->ctype = NULL;
  }
  st_insert(sc->type_table, self_entry->name, self_entry);

  return sc;
}

objc_scope_t
objc_scope_root_scope(objc_scope_t sc)
{
  while (sc->parent != NULL)
    sc = sc->parent;
  return sc;
}

void
objc_scope_set_type(objc_scope_t sc, objc_node_t spcr, objc_node_t dclr, char *name)
{
  objc_scope_entry_t entry;

  sc = objc_scope_root_scope(sc);
  entry = ALLOC(struct objc_scope_entry);
  entry->sc = sc;
  entry->type = OBJC_TYPE_NAME;
  entry->name = name;
  entry->ctype = NULL;
  entry->a.type.spcr = spcr;
  entry->a.type.dclr = dclr;
  entry->a.type.ctype = NULL;
  st_insert(sc->type_table, entry->name, entry);
}

void
objc_scope_set_struct(objc_scope_t sc, objc_node_t spcr, char *name)
{
  objc_scope_entry_t entry;

  sc = objc_scope_root_scope(sc);
  entry = ALLOC(struct objc_scope_entry);
  entry->sc = sc;
  entry->type = OBJC_TYPE_NAME;
  entry->name = name;
  entry->ctype = NULL;
  entry->a.type.spcr = spcr;
  entry->a.type.dclr = NULL;
  entry->a.type.ctype = NULL;
  st_insert(sc->struct_table, entry->name, entry);
}

objc_class_t
objc_scope_set_class(objc_scope_t sc, objc_node_t cls_spcr)
{
  objc_scope_entry_t entry = NULL, su_entry = NULL;
  objc_class_t cls;

  sc = objc_scope_root_scope(sc);
  if (cls_spcr->a.cls_spcr.super_name != NULL)
    st_lookup(sc->type_table,
              cls_spcr->a.cls_spcr.super_name,
              (st_data_t *)&su_entry);

  if (su_entry != NULL)
    cls = objc_make_class(cls_spcr->a.cls_spcr.name, su_entry->a.cls);
  else
    cls = objc_make_class(cls_spcr->a.cls_spcr.name, NULL);
  objc_register_class(cls);

  entry = ALLOC(struct objc_scope_entry);
  entry->sc = sc;
  entry->type = OBJC_CLASS_NAME;
  entry->name = cls->name;
  entry->ctype = NULL;
  entry->a.cls = cls;
  st_insert(sc->type_table, entry->name, entry);

  return cls;
}

void
objc_scope_set_alias_for_class(objc_scope_t sc,
                               const char *alias,
                               const char *real)
{
  objc_scope_entry_t entry = NULL;

  if (st_lookup(sc->type_table, real, (st_data_t *)&entry))
    st_insert(sc->type_table, objc_strdup(alias), entry);
}

void
objc_scope_set_var(objc_scope_t sc, objc_node_t spcr,
                   objc_node_t dclr, char *name)
{
  objc_scope_entry_t entry;

  entry = ALLOC(struct objc_scope_entry);
  entry->sc = sc;
  entry->type = OBJC_VAR_NAME;
  entry->name = name;
  entry->ctype = NULL;
  entry->a.type.spcr = spcr;
  entry->a.type.dclr = dclr;
  st_insert(sc->type_table, entry->name, entry);
}

objc_scope_entry_t
objc_scope_get_type(objc_scope_t sc, const char *name)
{
  objc_scope_entry_t entry = NULL;

  sc = objc_scope_root_scope(sc);
  st_lookup(sc->type_table, name, (st_data_t *)&entry);

  return entry;
}

objc_scope_entry_t
objc_scope_get_struct(objc_scope_t sc, const char *name)
{
  objc_scope_entry_t entry = NULL;

  sc = objc_scope_root_scope(sc);
  st_lookup(sc->struct_table, name, (st_data_t *)&entry);

  return entry;
}

objc_scope_entry_t
objc_scope_get_var(objc_scope_t sc, const char *name)
{
  objc_scope_entry_t entry = NULL;

  while (sc) {
    if (st_lookup(sc->type_table, name, (st_data_t *)&entry)) {
      if (entry->type == OBJC_VAR_NAME)
        return entry;
      else
        return NULL;
    } else
      sc = sc->parent;
  }

  return NULL;
}

objc_class_t
objc_scope_get_class(objc_scope_t sc)
{
  while (sc) {
    if (sc->cls)
      return sc->cls;
    sc = sc->parent;
  }
  return NULL;
}

const char *
objc_scope_entry_get_ctype(objc_scope_entry_t entry)
{
  if (entry->ctype)
    return entry->ctype;
  else {
    switch (entry->type) {
    case OBJC_COMPILER_BUILTIN:
      printf("warning: compiler builtin -- %s\n", entry->name);
      break;

    case OBJC_TYPE_NAME:
      entry->ctype = objc_ctype_from_decl(entry->sc,
                                          entry->a.type.spcr,
                                          entry->a.type.dclr);
      break;

    case OBJC_STRUCT_NAME:
    case OBJC_UNION_NAME:
      if (entry->a.type.spcr)
        entry->ctype = objc_ctype_from_decl(entry->sc,
                                            entry->a.type.spcr,
                                            entry->a.type.dclr);
      else {
        entry->ctype = ALLOC_N(char, 1);
        *entry->ctype = 0;
      }
      break;

    case OBJC_ENUM_NAME:
      entry->ctype = ALLOC_N(char, strlen(entry->name)+3);
      sprintf(entry->ctype, "%c%s%c", OBJC_CTYPE_ENUM_BEGIN_CODE,
              entry->name,
              OBJC_CTYPE_ENUM_END_CODE);
      break;

    case OBJC_CLASS_NAME:
    case OBJC_SELF:
      objc_print_error("objc_scope_get_ctype: not supported entry\n");
      break;

    case OBJC_VAR_NAME:
      entry->ctype = objc_ctype_from_decl(entry->sc,
                                          entry->a.type.spcr,
                                          entry->a.type.dclr);
      break;
    }
    return entry->ctype;
  }
}

void
objc_scope_init_method_definition()
{
  next_method_def = 1;
}

